/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */

const loaderUtils = require("loader-utils");

const WebWorkerTemplatePlugin = require("webpack/lib/webworker/WebWorkerTemplatePlugin");
const ExternalsPlugin = require("webpack/lib/ExternalsPlugin");
const NodeTargetPlugin = require("webpack/lib/node/NodeTargetPlugin");
const LoaderTargetPlugin = require("webpack/lib/LoaderTargetPlugin");
const SingleEntryPlugin = require("webpack/lib/SingleEntryPlugin");

const COMPILATION_METADATA = Symbol("COMPILATION_METADATA");

module.exports.COMPILATION_METADATA = COMPILATION_METADATA;

module.exports.pitch = function pitch(remainingRequest) {
	const { target, plugins = [], output, emit } = loaderUtils.getOptions(this) || {};

	if (target !== "worker") {
		throw new Error(`Unsupported compile target: ${JSON.stringify(target)}`);
	}

	this.cacheable(false);

	const { filename, options = {} } = getOutputFilename(output, { target });

	// eslint-disable-next-line no-underscore-dangle
	const currentCompilation = this._compilation;

	const outputFilename = loaderUtils.interpolateName(this, filename, {
		context: options.context || currentCompilation.options.context,
		regExp: options.regExp,
	});

	const outputOptions = {
		filename: outputFilename,
		chunkFilename: `${outputFilename}.[id]`,
		namedChunkFilename: null,
	};

	const compilerOptions = currentCompilation.compiler.options;
	const childCompiler = currentCompilation.createChildCompiler("worker", outputOptions, [
		// https://github.com/webpack/webpack/blob/master/lib/WebpackOptionsApply.js
		new WebWorkerTemplatePlugin(outputOptions),
		new LoaderTargetPlugin("webworker"),
		...(this.target === "web" || this.target === "webworker" ? [] : [new NodeTargetPlugin()]),

		// https://github.com/webpack-contrib/worker-loader/issues/95#issuecomment-352856617
		...(compilerOptions.externals ? [new ExternalsPlugin(compilerOptions.externals)] : []),

		...plugins,

		new SingleEntryPlugin(this.context, `!!${remainingRequest}`, "main"),
	]);

	const subCache = `subcache ${__dirname} ${remainingRequest}`;

	childCompiler.plugin("compilation", (compilation) => {
		if (!compilation.cache) {
			return;
		}
		if (!(subCache in compilation.cache)) {
			Object.assign(compilation.cache, { [subCache]: {} });
		}
		Object.assign(compilation, { cache: compilation.cache[subCache] });
	});

	const callback = this.async();

	childCompiler.runAsChild((error, entries, compilation) => {
		if (error) {
			return callback(error);
		}
		if (entries.length === 0) {
			return callback(null, null);
		}
		const mainFilename = entries[0].files[0];
		if (emit === false) {
			delete currentCompilation.assets[mainFilename];
		}
		callback(null, compilation.assets[mainFilename].source(), null, {
			[COMPILATION_METADATA]: entries[0].files,
		});
	});
};

function getOutputFilename(options, { target }) {
	if (!options) {
		return { filename: `[hash].${target}.js`, options: undefined };
	}
	if (typeof options === "string") {
		return { filename: options, options: undefined };
	}
	if (typeof options === "object") {
		return {
			filename: options.filename,
			options: {
				context: options.context,
				regExp: options.regExp,
			},
		};
	}
	throw new Error(`Invalid compile output options: ${options}`);
}
